﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using uTILLIty.Outlook.FlexFileAddIn.Properties;
using OL = Microsoft.Office.Interop.Outlook;

namespace uTILLIty.Outlook.FlexFileAddIn
{
  public class FolderPopulationBackgroundWorker : BackgroundWorker
  {
    private const int DisplayPriorityConversation = 200;
    private const int DisplayPriorityName = 100;
    private const int DisplayPriorityDomain = 300;
    //public IEnumerable<FolderInfo> CandidateFolders { get; protected set; }

    public FolderPopulationBackgroundWorker()
    {
      DoWork += DoWorkInternal;
      WorkerSupportsCancellation = true;
    }

    //public FolderPopulationArgs Args { get; set; }
    private FolderPopulationArgs _currentArgs;

    private void DoWorkInternal(object sender, DoWorkEventArgs e)
    {
#if DEBUG
      System.Diagnostics.Debug.WriteLine("FolderPopulationBackgroundWorker.DoWorkInternal");
#endif

      _currentArgs = (FolderPopulationArgs)e.Argument;
      if (_currentArgs == null)
        throw new InvalidOperationException("Must set Args first!");

      FilterList();
      e.Result = _currentArgs.TargetList;
    }

    public void FilterList()
    {
      try
      {
        var text = _currentArgs.Filter;
        FillCandidateFolders();

        if (string.IsNullOrWhiteSpace(text))
        {
          foreach (var folder in GetDefaultList())
            AddFolder(folder);
        }
        else
        {
          //lock (MapiFolderManager.FolderList) //causes deadlock
          {
            var list = _currentArgs.MapiFolderManager.FolderList
              .Where(l => l.Name.IndexOf(text, StringComparison.CurrentCultureIgnoreCase) >= 0)
              .Select(l => new CandidateFolderInfo(l, DisplayPriorityName, Resources.MatchByNameDescription))
              .ToArray();
            foreach (var entry in list)
            {
              //entry.IsMailBasedCandidate = false;
              AddFolder(entry);
            }
          }
        }
      }
      catch (InvalidOperationException)
      {
        //enumeration was invalid as list was changed in background >> try again
        //FilterList();
      }
			catch (OperationCanceledException)
			{}
    }

    private void AddFolder(CandidateFolderInfo entry)
    {
      if (_currentArgs.TargetList.Contains(entry))
        return;
      _currentArgs.TargetList.Add(entry);
    }

    protected virtual void FillCandidateFolders()
    {
      var selection = _currentArgs.SelectedItems;
      if (selection.Count == 0)
        return;

      try
      {
        DetectAllCandidateFolders(selection.Take(10).ToArray());
      }
      catch (COMException ex)
      {
        /* occurs during outlook-startup
          System.Runtime.InteropServices.COMException was unhandled by user code
            HResult=-2147467259
            Message=Das an die Methode übergebene Element ist in dieser Unterhaltung nicht vorhanden.
            Source=Microsoft Outlook
            ErrorCode=-2147467259
            StackTrace:
                 bei Microsoft.Office.Interop.Outlook._Conversation.GetChildren(Object Item)
                 bei uTILLIty.Outlook.FlexFileAddIn.FilterableFolderListController.DetectCandidateFoldersByConversation(IEnumerable`1 selection, BindingList`1 candidateFolders) in c:\DEV\CodePlex\OutlookFlexFile\OutlookFlexFileAddIn\FilterableFolderListController.cs:Zeile 311.
                 bei uTILLIty.Outlook.FlexFileAddIn.FilterableFolderListController.OnExplorerSelectionChanged() in c:\DEV\CodePlex\OutlookFlexFile\OutlookFlexFileAddIn\FilterableFolderListController.cs:Zeile 90.
            InnerException: 

         * */
        if (ex.ErrorCode != -2147467259)
          throw;
      }
      //FindFoldersBySender(selection, candidateFolders);

      RemoveUnnessesaryFolders();
    }

    private void RemoveUnnessesaryFolders()
    {
      var targetFolderId = _currentArgs.Application.ActiveExplorer().CurrentFolder.EntryID;
      TryRemoveFolder(targetFolderId);
      var blacklistedFolderIds = _currentArgs.MapiFolderManager.GetBlacklistedFolderIds();
      foreach (var folderId in blacklistedFolderIds)
        TryRemoveFolder(folderId);
    }

    private void TryRemoveFolder(string targetFolderId)
    {
      var targetFolder = _currentArgs.TargetList.FirstOrDefault(f => targetFolderId.Equals(f.FolderId));
      if (targetFolder != null)
        _currentArgs.TargetList.Remove(targetFolder);
    }

    //private void FindFoldersBySender(List<OL.MailItem> selection, BindingList<FolderInfo> candidateFolders)
    //{
    //  //http://www.add-in-express.com/creating-addins-blog/2012/05/31/outlook-search-csharp-vbnet/
    //  //Application.AdvancedSearch()
    //}

    private IEnumerable<CandidateFolderInfo> GetDefaultList()
    {
      return new CandidateFolderInfo[0];
    }

    private void DetectAllCandidateFolders(OL.ItemEvents_10_Event[] selection)
    {
			CheckShouldCancel();
			DetectCandidateFoldersByConversation(selection);
	    CheckShouldCancel();
      DetectCandidateFoldersByDomain(selection);
    }

    private void DetectCandidateFoldersByDomain(IEnumerable<OL.ItemEvents_10_Event> selection)
    {
      foreach (var curItem in selection)
      {
				CheckShouldCancel();
				
				IEnumerable<string> companyNames = null;
        {
          var item = curItem as OL.MailItem;
          if (item != null)
            companyNames = GetCompanyNames(item.Recipients)
                         .Union(new[] { ExtractCompanyName(item.SenderEmailAddress) });
        }
        {
          var item = curItem as OL.MeetingItem;
          if (item != null)
            companyNames = GetCompanyNames(item.Recipients)
                         .Union(new[] { ExtractCompanyName(item.SenderEmailAddress) });
        }
        {
          var item = curItem as OL.AppointmentItem;
          if (item != null)
            companyNames = GetCompanyNames(item.Recipients)
                         .Union(new[] { ExtractCompanyName(item.Organizer) });
        }
        if (companyNames == null)
          continue;

        foreach (var companyName in companyNames)
        {
          if (companyName == null)
            continue;
					CheckShouldCancel();

          var companyFolders = _currentArgs.MapiFolderManager.FolderList
            .Where(f => f.Name.ToLowerInvariant().Contains(companyName))
            .Select(f => new CandidateFolderInfo(f, DisplayPriorityDomain, Resources.MatchByDomainDescription, Color.Gray, Color.White))
            .ToArray();
          foreach (var folder in companyFolders)
          {
            //folder.IsMailBasedCandidate = true;
            AddFolder(folder);
          }
        }
      }
    }

    protected virtual IEnumerable<string> GetCompanyNames(OL.Recipients recipients)
    {
      var companyNames = recipients.Cast<OL.Recipient>().Select(r => ExtractCompanyName(r.Address));
      return companyNames;
    }

    private static readonly Regex EmailCompanyNameRegex = new Regex(@"(?in)@(?<company>[^\.]+)");
    private string ExtractCompanyName(string address)
    {
      //TODO: fix matching of multi-part-domains (ie. "office@subsidary.somecompany.com")
      var match = EmailCompanyNameRegex.Match(address ?? "");
      return match.Success ? match.Groups["company"].Value.ToLowerInvariant() : null;
    }

    private void DetectCandidateFoldersByConversation(IEnumerable<OL.ItemEvents_10_Event> selection)
    {
      foreach (var curItem in selection)
      {
				CheckShouldCancel();
				
				var item = curItem as OL.MailItem;
        if (item == null)
          continue;
        OL.Conversation conversation = item.GetConversation();
        OL.SimpleItems simpleItems = conversation.GetRootItems();

        foreach (object ri in simpleItems)
        {
          var rmi = ri as OL.MailItem;
          if (rmi != null)
            AddFolderToList(rmi, DisplayPriorityConversation, Resources.MatchByConversationDescription, Color.Black, Color.Silver);
          OL.SimpleItems children = conversation.GetChildren(ri);
          foreach (object rci in children)
          {
						CheckShouldCancel();
						
						var icr = rci as OL.MailItem;
            if (icr != null)
              AddFolderToList(icr, DisplayPriorityConversation, Resources.MatchByConversationDescription, Color.Black, Color.Silver);
          }
        }
      }
    }

    private void AddFolderToList(OL.MailItem mailItem, int displayPriority, string candidateReasonDescription, Color foreColor, Color backColor)
    {
      CandidateFolderInfo folder = GetFolder(mailItem, displayPriority, candidateReasonDescription);
      if (folder == null)
        return;
      
      folder.ForeColor = foreColor;
      folder.BackColor = backColor;
      AddFolder(folder);
    }

    private CandidateFolderInfo GetFolder(OL.MailItem mailItem, int displayPriority, string candidateReasonDescription)
    {
      var curItem = mailItem.Parent;
      OL.Folder folder = null;
      while (curItem != null && folder == null)
      {
        folder = curItem as OL.Folder;
        curItem = curItem.Parent;
      }
      if (folder == null)
        return null;
      var fi = _currentArgs.MapiFolderManager.FolderList.FirstOrDefault(f => f.FolderId == folder.EntryID);
      if (fi != null)
      {
        return new CandidateFolderInfo(fi, displayPriority, candidateReasonDescription);
        //fi.IsMailBasedCandidate = isMailBasedCandidate;
      }
      return null;
    }

    private void CheckShouldCancel()
    {
      if (!CancellationPending)
        return;

#if DEBUG
      System.Diagnostics.Debug.WriteLine("FolderPopulationBackgroundWorker.CancellationPending!");
#endif
			throw new OperationCanceledException();
    }

    public class FolderPopulationArgs
    {
      public FolderPopulationArgs(OL.Application application, MapiFolderManager mapiFolderManager, SynchronizationContext synchronizationContext, string filter, List<OL.ItemEvents_10_Event> selectedItems)
      {
        if (application == null)
          throw new ArgumentNullException("application");
        if (mapiFolderManager == null)
          throw new ArgumentNullException("mapiFolderManager");
        if (synchronizationContext == null)
          throw new ArgumentNullException("synchronizationContext");

        Filter = filter;
        SelectedItems = selectedItems;
        Application = application;
        MapiFolderManager = mapiFolderManager;
        TargetList = new SynchronizedBindingList<CandidateFolderInfo>(synchronizationContext);
      }

      public string Filter { get; private set; }
      public List<OL.ItemEvents_10_Event> SelectedItems { get; private set; }
      public OL.Application Application { get; private set; }
      public MapiFolderManager MapiFolderManager { get; set; }
      public SynchronizedBindingList<CandidateFolderInfo> TargetList { get; set; }
    }
  }
}